'use strict';
var _ = require('lodash');
var Injector = require('injector');
var util = require('util');

module.exports = Injector.define([
    'constant/error_code',
    'constant/error_message'
], createModule);

function createModule(ERROR_CODE, ERROR_MESSAGE) {
    var ERROR_NAME = _.invert(ERROR_CODE);

    function AppError(code, message, data, meta) {
        if (!_.isString(code)&&!_.isNumber(code)) {
            throw new TypeError('The parameter <code> should be a string or number');
        }
        this._code = code;
        this._message = message;
        this._data = data;
        this._meta = meta;

        this._setOriginal();
    }

    util.inherits(AppError, Error);

    AppError.prototype._code = undefined;
    AppError.prototype._message = undefined;
    AppError.prototype._data = undefined;
    AppError.prototype._meta = undefined;

    AppError.prototype._setOriginal = function () {
        this.code = this._code;
        this.message = this._message;
    };

    AppError.prototype.setMessage = function (message) {
        this._message = undefined;
        if (_.isString(message)) {
            this._message = message;
        } else if (_.isObject(message)) {
            if (_.isFunction(message.getMessage)&&_.isString(message.getMessage())) {
                this._message = message.getMessage();
            } else if (_.isString(message.message)) {
                this._message = message.message;
            }
        }
        this._setOriginal();
        return this;
    };

    AppError.prototype.setData = function (data) {
        this._data = data;
        return this;
    };

    AppError.prototype.setMeta = function (meta) {
        this._meta = meta;
        return this;
    };

    AppError.prototype.getMessage = function () {
        return this._message;
    };

    AppError.prototype.getCode = function () {
        return this._code;
    };

    AppError.prototype.getData = function () {
        return this._data;
    };
    AppError.prototype.getMeta = function () {
        return this._meta;
    };

    AppError.prototype.toJson = function () {
        return _.extend({}, this._meta, {
            code: this._code,
            message: this._message,
            data: this._data
        });
    };

    AppError.prototype.toJsonString = function () {
        return JSON.stringify(this.toJson());
    };

    AppError.create = function (codeName) {
        if (!(codeName in ERROR_CODE)) {
            throw new Error('There is not a error named "' + codeName + '"');
        }
        return new AppError(ERROR_CODE[codeName], ERROR_MESSAGE[codeName]);
    };

    AppError.createWithCode = function (code) {
        if (!(code in ERROR_NAME)) {
            throw new Error('There is not a error coded "' + code + '"');
        }
        var errorName = ERROR_NAME[code];
        return new AppError(ERROR_CODE[errorName], ERROR_MESSAGE[errorName]);
    };

    AppError.hasInstance = function (instance) {
        return _.isObject(instance) && (instance instanceof AppError);
    };

    _.each(ERROR_CODE, function (code, codeName) {
        Object.defineProperty(AppError, codeName, {
            get: function () {
                return new AppError(code, ERROR_MESSAGE[codeName]);
            }
        });
    });

    return AppError;
}
